// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.asm;

import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.objectweb.asm.tree.analysis.Frame;

import com.dustedpixels.asm.analysis.Analyzers;
import com.dustedpixels.asm.analysis.LocalsUsageInterpreter;
import com.dustedpixels.asm.analysis.LocalsUsageInterpreter.LocalsUsageValue;


/**
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public final class AnalysisUtils {
  /**
   * Analyses usage of each local variable slot for every instruction in method
   * code.
   * 
   * <p>
   * Returns two-dimensional boolean array indexed by instruction index and
   * local slot index, in which {@code false} indicates that the value stored at
   * given local slot will be never used by given instruction and the code
   * following that instruction. Value {@code true} indicates that the value
   * stored at given local slot will (or may) be read by given instruction or
   * one of the following code.
   * 
   * <p>
   * The result of this calculation may be used to detect re-usable local slots
   * when performing method inlining.
   * 
   * @param className internal class name
   * @param method a method to analyse
   * @return the result of the analysis
   */
  public static boolean[][] analyseLocalsUsage(String className, MethodNode method) {
    try {
      LocalsUsageInterpreter interpreter = new LocalsUsageInterpreter();
      Analyzer analyzer = Analyzers.newUsing(interpreter);
      Frame[] frames = analyzer.analyze(className, method);
      boolean[][] localsUsage = new boolean[frames.length][method.maxLocals];
      for (int insnIndex = 0; insnIndex < frames.length; insnIndex++) {
        boolean doubleValue = false;
        for (int localIndex = 0; localIndex < method.maxLocals; localIndex++) {
          if (!doubleValue) {
            LocalsUsageValue value = (LocalsUsageValue) frames[insnIndex].getLocal(localIndex);
            localsUsage[insnIndex][localIndex] = value.isUsed();
            doubleValue = (value.getSize() == 2);
          } else {
            localsUsage[insnIndex][localIndex] = localsUsage[insnIndex][localIndex - 1];
            doubleValue = false;
          }
        }
      }
      return localsUsage;
    } catch (AnalyzerException e) {
      throw new IllegalArgumentException(e);
    }
  }
  
  /**
   * Calculates maxLocal value for each instruction in the bytecode.
   * 
   * @param className class name
   * @param method a method to analyze
   * @return an array with maxLocals for each instruction.
   */
  public static int[] calculateMaxLocals(String className, MethodNode method) {
    try {
      LocalsUsageInterpreter interpreter = new LocalsUsageInterpreter();
      Analyzer analyzer = Analyzers.newUsing(interpreter);
      Frame[] frames = analyzer.analyze(className, method);
      int[] maxLocals = new int[frames.length];
      for (int insnIndex = 0; insnIndex < frames.length; insnIndex++) {
        for (int localIndex = 0; localIndex < method.maxLocals; localIndex++) {
          LocalsUsageValue value = (LocalsUsageValue) frames[insnIndex].getLocal(localIndex);
          if (value.isUsed()) {
            maxLocals[insnIndex] = localIndex + value.getSize() - 1;
          }
          if (value.getSize() == 2) localIndex++;
        }
      }
      return maxLocals;
    } catch (AnalyzerException e) {
      throw new IllegalArgumentException(e);
    }
  }

  /**
   * Calculates maxLocal value at the point before execution of given 
   * instruction in given method.
   * 
   * @param className class name
   * @param method a method to analyze
   * @param insn instruction of the method
   * @return an array with maxLocals for each instruction.
   */
  public static int calculateMaxLocal(
      String className,
      MethodNode method,
      AbstractInsnNode insn) {    
    try {
      // Perform local usage analysis.
      LocalsUsageInterpreter interpreter = new LocalsUsageInterpreter();
      Analyzer analyzer = Analyzers.newUsing(interpreter);
      Frame[] frames = analyzer.analyze(className, method);
      
      // Detect last used local at given instruction.
      int maxLocal = 0;
      int insnIndex = method.instructions.indexOf(insn);
      for (int localIndex = 0; localIndex < method.maxLocals; localIndex++) {
        LocalsUsageValue value = (LocalsUsageValue) frames[insnIndex].getLocal(localIndex);
        if (value.isUsed()) maxLocal = localIndex + value.getSize();
        if (value.getSize() == 2) localIndex++;
      }
      return maxLocal;
    } catch (AnalyzerException e) {
      throw new IllegalArgumentException(e);
    }
  }

  /**
   * Calculates stack size at the point before execution of given 
   * instruction in given method.
   * 
   * @param className class name
   * @param method a method to analyze
   * @param insn instruction of the method
   * @return an array with maxLocals for each instruction.
   */
  public static int calculateStackSize(
      String className,
      MethodNode method,
      AbstractInsnNode insn) {    
    try {
      // Get index of given instruction.
      int insnIndex = method.instructions.indexOf(insn);
      
      // Perform local usage analysis.
      BasicInterpreter interpreter = new BasicInterpreter();
      Analyzer analyzer = Analyzers.newUsing(interpreter);
      Frame[] frames = analyzer.analyze(className, method);
      
      return frames[insnIndex].getStackSize();
    } catch (AnalyzerException e) {
      throw new IllegalArgumentException(e);
    }
  }
}
